package com.weipeep.common.utils.httpclient;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Future;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.StatusLine;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.conn.NoopIOSessionStrategy;
import org.apache.http.nio.conn.SchemeIOSessionStrategy;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.protocol.HttpContext;
import com.weipeep.common.utils.httpclient.HttpClientService_3_2.X509TrustManager;
import com.weipeep.common.utils.httpclient.HttpRequest.Method;
import com.weipeep.common.utils.httpclient.util.IOUtil;


public class AsyncHttpClientService implements HttpService {
	CloseableHttpAsyncClient httpclient;

	HttpAsyncClientBuilder builder = null;
	
	HttpClientContext localContext = HttpClientContext.create();
	  
	RequestConfig defaultRequestConfig = RequestConfig.custom()
    .setCookieSpec(CookieSpecs.BEST_MATCH)
    .setExpectContinueEnabled(true)
    .setStaleConnectionCheckEnabled(true)
    .setRedirectsEnabled(true).setRelativeRedirectsAllowed(true)
    .setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.DIGEST))
    .setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC))
    .build();

	public AsyncHttpClientService() {
		builder = HttpAsyncClients.custom();
		bindInterceptor();
		try {
			init();
		} catch (Exception e) {
			e.printStackTrace();
		}
		httpclient.start();
	}
	@Override
	public HttpResponse execute(HttpRequest req) throws Exception {
		long start = System.currentTimeMillis();
		HttpRequestBase requestBase = getHttpRequestBase(req);
	   try{
		   Future<org.apache.http.HttpResponse> future	= httpclient.execute(requestBase,localContext, null);
			org.apache.http.HttpResponse httpResponse = future.get();
			StatusLine line = httpResponse.getStatusLine();
			
			HttpResponse res = new HttpResponse(line.getStatusCode(),line.getReasonPhrase());
			/*返回头文件*/
			for(Header header: httpResponse.getAllHeaders()){
				if("Set-Cookie".equals(header.getName())&&res.getHeader("Set-Cookie")!=null){
					res.setHeader(header.getName(), res.getHeader("Set-Cookie")+","+header.getValue());
					continue;
				}
				res.setHeader(header.getName(), header.getValue());
			};
			
			/*返回cookies*/
			for(Cookie co: localContext.getCookieStore().getCookies()){
				res.setCookie(co.getName(), co.getValue());
			}
			
			ByteArrayOutputStream outputStream =  new ByteArrayOutputStream();
			
			HttpEntity entity = httpResponse.getEntity();
			if("gzip".equals(res.getHeader("Content-Encoding"))){
				entity = new GzipDecompressingEntity(entity);
			}
			InputStream in = entity.getContent();
			IOUtil.copy(in, outputStream);
			
			/*返回流 byte[] ,返回流*/
			res.setResponseData(outputStream.toByteArray());
			return res;
	   }finally{
		   	if(requestBase!=null){
		    	requestBase.reset();
		    	requestBase = null;
		    }
			long l = (System.currentTimeMillis()-start);
			String body ="";
			if(req.getPostParams().isEmpty()){
				if(req.getRequestBody()!=null){
					body+=  new String(req.getRequestBody(), req.getCharset());
				}
			}
			System.out.println(l+"::URL::" +req.getUrl()+"::"+body);
	   }
		
	}
	
	

	public void init() throws IOReactorException, NoSuchAlgorithmException, KeyManagementException {

		IOReactorConfig ioReactorConfig = IOReactorConfig.custom().
			setIoThreadCount(Runtime.getRuntime().availableProcessors()).setConnectTimeout(120000).setSoTimeout(120000).build();

		ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
		SSLContext sslcontext = SSLContext.getInstance("TLS");
		sslcontext.init(null, new TrustManager[]{new X509TrustManager()}, null);
		Registry<SchemeIOSessionStrategy> sessionStrategyRegistry = RegistryBuilder.<SchemeIOSessionStrategy> create()
			.register("http", NoopIOSessionStrategy.INSTANCE)
			.register("https", new SSLIOSessionStrategy(sslcontext, new BrowserCompatHostnameVerifier())).build();

		PoolingNHttpClientConnectionManager manager = new PoolingNHttpClientConnectionManager(ioReactor, sessionStrategyRegistry);

		CookieStore cookieStore = new BasicCookieStore();
		localContext.setCookieStore(cookieStore);
		
		httpclient = builder.setConnectionManager(manager).setDefaultRequestConfig(defaultRequestConfig)
		.setDefaultCookieStore(cookieStore).build();
	}

	@Override
	public void bindInterceptor() {
		builder.addInterceptorFirst(new HttpRequestInterceptor() {
			@Override
			public void process(org.apache.http.HttpRequest req, HttpContext arg1) throws HttpException, IOException {
				if(!req.containsHeader("Accept-Encoding")){
					req.addHeader("Accept-Encoding", "gzip,deflate,sdch");
				}
			}
		});
		
	}

	HttpRequestBase requestBase = null;
	@Override
	public HttpRequestBase getHttpRequestBase(HttpRequest req) {
		if (req.getMethod() == Method.post) {
			requestBase = new HttpPost(req.getUrl());

			if (req.getFileMap().size() > 0) {
				parseMartipart(req);
			} else if (req.getPostParams().size() > 0) {
				parsePostParams(req);
			}
			if(req.getRequestBody() != null){
				ByteArrayEntity entity = new ByteArrayEntity(req.getRequestBody());
				((HttpPost)requestBase).setEntity(entity);
			}
		} else {
			if (req.getPostParams().size()>0) {
				String spe = req.getUrl().indexOf("?") != -1 ? "&" : "?";
				requestBase = new HttpGet(req.getUrl() + spe + req.postParamsToString());
			}else{
				requestBase = new HttpGet(req.getUrl());
			}
		}

		RequestConfig requestConfig = RequestConfig.custom()
			.setSocketTimeout(req.getConnectTimeout()).setConnectTimeout(req.getConnectTimeout())
			.setConnectionRequestTimeout(req.getReadTimeout()).build();
		requestBase.setConfig(requestConfig);

		parseHeader(req);
		parseCookies(req);
		
		return requestBase;
	}

	@Override
	public void parseHeader(HttpRequest req) {
		Map<String, String> headers = req.getHeaders();
		for (String key : headers.keySet()) {
			requestBase.setHeader(key, headers.get(key));
		}
	}

	@Override
	public void parseCookies(HttpRequest req) {
		Map<String, String> cookies = req.getCookies();
		for (String key : cookies.keySet()) {
			localContext.getCookieStore().addCookie(new BasicClientCookie(key, cookies.get(key)));
		}
	}
	@Override
	public void removeAllCookie() {
		localContext.getCookieStore().clear();
	}
	
	@Override
	public void removeCookie(String name) {
		Iterator<Cookie> ite = localContext.getCookieStore().getCookies().iterator();
		 while(ite.hasNext()){
			 if(ite.next().getName().equals(name)){
				 ite.remove();
			 }
		 }
	}
	

	@Override
	public void parsePostParams(HttpRequest req) {
		ContentType contentType = ContentType.create("application/x-www-form-urlencoded", Charset.forName(req.getCharset()));
		StringEntity entity = new StringEntity(req.postParamsToString(), contentType);
		((HttpPost) requestBase).setEntity(entity);
	}

	@Override
	public void parseMartipart(HttpRequest req) {
		MultipartEntity entity = new MultipartEntity();
		String charset = req.getCharset();

		Map<String, String> postMap = req.getPostParams();
		for (String key : postMap.keySet()) {
			String value = postMap.get(key);
			value = value == null ? "" : value;
			try {
				entity.addPart(key, new StringBody(value, Charset.forName(charset)));
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}

		Map<String, File> fileMap = req.getFileMap();
		for (String key : fileMap.keySet()) {
			File value = fileMap.get(key);
			entity.addPart(new FormBodyPart(key, new FileBody(value)));
		}

		((HttpPost) requestBase).setEntity(entity);
	}

	private String getMimeType(File file) {
		FileNameMap fileNameMap = URLConnection.getFileNameMap();
		return fileNameMap.getContentTypeFor(file.toString());
	}

	@Override
	public void shutdown() {
		try{
			httpclient.close();
		}catch (Exception e) {
		}
	}
	
	@Override
	public boolean isShutdown() {
		return !httpclient.isRunning();
	}

}
